

#include "qe_serial.h"
#include "qe_arch.h"


static void _serial_check_buffer_size(void)
{
    static qe_bool_t already_output = qe_false;

    if (already_output == qe_false)
    {
		dev_warn("there is no enough buffer for saving data");
        already_output = qe_true;
    }
}

static int serial_poll_rx(struct qe_serial_device *serial, qe_u8 *data, int length)
{
	int c;
	int size;

	qe_assert(serial != QE_NULL);
	size = length;

	while (length) {
		c = serial->ops->getc(serial);
		if (c == -1) break;

		*data = c;
		data++; length--;

		if (serial->parent.open_flag & QE_DEV_F_STREAM) {
			if (c == '\n') break;
		}
	}

	return size - length;
}


static inline int serial_int_rx(struct qe_serial_device *serial, qe_u8 *data, int length)
{
	int size;
	struct qe_serial_rxfifo* rxfifo;

	qe_assert(serial != QE_NULL);
	size = length;

	rxfifo = (struct qe_serial_rxfifo *)serial->rx;
	qe_assert(rxfifo != QE_NULL);

	while (length) 
	{
		int c;

		/* disable interrupt */
		//qe_interrupt_disable();

		/* there's no data: */
		if ((rxfifo->get_index == rxfifo->put_index) && (rxfifo->is_full == qe_false))
		{
			/* no data, enable interrupt and break out */
			//qe_interrupt_enable();
			break;
		}

		/* otherwise there's the data: */
		c = rxfifo->buffer[rxfifo->get_index];
		rxfifo->get_index += 1;
		if (rxfifo->get_index >= serial->config.bufsz) rxfifo->get_index = 0;

		if (rxfifo->is_full == qe_true)
		{
			rxfifo->is_full = qe_false;
		}

		/* enable interrupt */
		//qe_interrupt_enable();

		*data = c & 0xff;
        data ++; length --;
	}

	return size - length;
}

#if (QE_SERIAL_WITH_DMA == 1)
static qe_size_t _serial_fifo_calc_recved_len(struct qe_serial_device *serial)
{
    struct qe_serial_rxfifo *rxfifo = (struct qe_serial_rxfifo *) serial->rx;

    qe_assert(rxfifo != QE_NULL);

    if (rxfifo->put_index == rxfifo->get_index)
    {
        return (rxfifo->is_full == qe_false ? 0 : serial->config.bufsz);
    }
    else
    {
        if (rxfifo->put_index > rxfifo->get_index)
        {
            return rxfifo->put_index - rxfifo->get_index;
        }
        else
        {
            return serial->config.bufsz - (rxfifo->get_index - rxfifo->put_index);
        }
    }
}

/**
 * Calculate DMA received data length.
 *
 * @param serial serial device
 *
 * @return length
 */
static qe_size_t qe_dma_calc_recved_len(struct qe_serial_device *serial)
{
    return _serial_fifo_calc_recved_len(serial);
}

/**
 * Read data finish by DMA mode then update the get index for receive fifo.
 *
 * @param serial serial device
 * @param len get data length for this operate
 */
static void qe_dma_recv_update_get_index(struct qe_serial_device *serial, 
	qe_size_t len)
{
    struct qe_serial_rxfifo *rx_fifo = (struct qe_serial_rxfifo *) serial->rx;

    qe_assert(rx_fifo != QE_NULL);
    qe_assert(len <= qe_dma_calc_recved_len(serial));

    if (rx_fifo->is_full && len != 0) rx_fifo->is_full = qe_false;

    rx_fifo->get_index += len;
    if (rx_fifo->get_index >= serial->config.bufsz)
    {
        rx_fifo->get_index %= serial->config.bufsz;
    }
}

/**
 * DMA received finish then update put index for receive fifo.
 *
 * @param serial serial device
 * @param len received length for this transmit
 */
static void qe_dma_recv_update_put_index(struct qe_serial_device *serial, 
	qe_size_t len)
{
    struct qe_serial_rxfifo *rxfifo = (struct qe_serial_rxfifo *)serial->rx;

    qe_assert(rxfifo != QE_NULL);

    if (rxfifo->get_index <= rxfifo->put_index)
    {
        rxfifo->put_index += len;
        /* beyond the fifo end */
        if (rxfifo->put_index >= serial->config.bufsz)
        {
            rxfifo->put_index %= serial->config.bufsz;
            /* force overwrite get index */
            if (rxfifo->put_index >= rxfifo->get_index)
            {
                rxfifo->is_full = qe_true;
            }
        }
    }
    else
    {
        rxfifo->put_index += len;
        if (rxfifo->put_index >= rxfifo->get_index)
        {
            /* beyond the fifo end */
            if (rxfifo->put_index >= serial->config.bufsz)
            {
                rxfifo->put_index %= serial->config.bufsz;
            }
            /* force overwrite get index */
            rxfifo->is_full = qe_true;
        }
    }

    if(rxfifo->is_full == qe_true)
    {
        _serial_check_buffer_size();
        rxfifo->get_index = rxfifo->put_index;
    }
}

static inline int serial_dma_rx(struct qe_serial_device *serial, qe_u8 *data, int length)
{
	qe_assert(serial != QE_NULL);
	qe_assert(data != QE_NULL);

	qe_interrupt_disable();

	if (serial->config.bufsz == 0)
	{
		qe_err_t ret = qe_ok;
		struct qe_serial_rxdma *rxdma;

		rxdma = (struct qe_serial_rxdma *)serial->rx;
		qe_assert(rxdma != QE_NULL);

		if (rxdma->activated != qe_true)
		{
			rxdma->activated = qe_true;
			qe_assert(serial->ops->dma_transmit != QE_NULL);
			serial->ops->dma_transmit(serial, data, length, QE_SERIAL_DMA_RX);
		}
		else ret = qe_err_busy;
		qe_interrupt_enable();

		if (ret == qe_ok) return length;

		qe_set_errno(ret);
		return 0;
	}
	else 
	{
		struct qe_serial_rxfifo *rxfifo = (struct qe_serial_rxfifo *)serial->rx;
		qe_size_t recv_len = 0, fifo_recved_len = qe_dma_calc_recved_len(serial);
		qe_assert(rxfifo != QE_NULL);
		if (length < (int)fifo_recved_len)
            recv_len = length;
        else
            recv_len = fifo_recved_len;
		if (rxfifo->get_index + recv_len < serial->config.bufsz)
			qe_memcpy(data, rxfifo->buffer + rxfifo->get_index, recv_len);
		else
		{
			qe_memcpy(data, rxfifo->buffer + rxfifo->get_index,
            	serial->config.bufsz - rxfifo->get_index);
            qe_memcpy(data + serial->config.bufsz - rxfifo->get_index, rxfifo->buffer,
                recv_len + rxfifo->get_index - serial->config.bufsz);
		}
		qe_dma_recv_update_get_index(serial, recv_len);
        qe_interrupt_enable();
		return recv_len;
	}
}
#endif

static inline int serial_int_tx(struct qe_serial_device *serial, const qe_u8 *data, int length)
{
	int size;
	struct qe_serial_txfifo *txfifo;

	qe_assert(serial != QE_NULL);

	size = length;
	txfifo = (struct qe_serial_txfifo*) serial->tx;
	qe_assert(txfifo != QE_NULL);

	while (length)
	{
        /*
         * to be polite with serial console add a line feed
         * to the carriage return character
         */
        if (*data == '\n' && (serial->parent.open_flag & QE_DEV_F_STREAM))
        {
			if (serial->ops->putc(serial, '\r') == -1) 
			{
				//qe_completion_wait(txfifo->completion, QE_WAIT_FOREVER);
				continue;
			}
		}

		if (serial->ops->putc(serial, *(char*)data) == -1)
		{
			//qe_completion_wait(txfifo->completion, QE_WAIT_FOREVER);
			continue;
		}

		data++; length--;
	}

	return size - length;
}

static inline int serial_poll_tx(struct qe_serial_device *serial, const qe_u8 *data,
	int length)
{
	int size;
	qe_assert(serial != QE_NULL);

	size = length;

	while (length)
	{
        /*
         * to be polite with serial console add a line feed
         * to the carriage return character
         */
        if (*data == '\n' && (serial->parent.open_flag & QE_DEV_F_STREAM))
        {
			serial->ops->putc(serial, '\r');
		}

		serial->ops->putc(serial, *data);

		++data; --length;
	}

	return size - length;
}

static inline int serial_dma_tx(struct qe_serial_device *serial, 
	const qe_u8 *data, int length)
{
	(void)serial;
	(void)data;
	(void)length;
	return 0;
}

static qe_err_t qe_serial_init(struct qe_device *dev)
{
	qe_err_t ret = qe_ok;
	struct qe_serial_device *serial;

	qe_assert(dev != QE_NULL);

	serial = (struct qe_serial_device *)dev;

	serial->rx = QE_NULL;
	serial->tx = QE_NULL;

	if (serial->ops->configure) {
		ret = serial->ops->configure(serial, &serial->config);
	}

	return ret;
}

static struct qe_serial_rxfifo *serial_rxfifo_create(qe_size_t size)
{
    qe_u8 *p;
	struct qe_serial_rxfifo *rxfifo;
	rxfifo = (struct qe_serial_rxfifo *)qe_malloc(sizeof(struct qe_serial_rxfifo) + size);
	qe_assert(rxfifo != QE_NULL);
    p = (qe_u8 *)rxfifo;
	rxfifo->buffer = p + sizeof(struct qe_serial_rxfifo);
	qe_memset(rxfifo->buffer, 0, size);
	rxfifo->put_index = 0;
	rxfifo->get_index = 0;
	rxfifo->is_full = qe_false;
	return rxfifo;
}

/* ISR for serial interrupt */
void qe_hw_serial_isr(struct qe_serial_device *serial, int event)
{
	switch (event & 0xff)
	{
		case QE_SERIAL_EVENT_RX_IND:
		{
			int c = -1;
			qe_base_t level;
			struct qe_serial_rxfifo *rxfifo;

			/* interrupt mode receive */
			rxfifo = (struct qe_serial_rxfifo*)serial->rx;
			qe_assert(rxfifo != QE_NULL);

			while (1) 
			{
				c = serial->ops->getc(serial);
				if (c == -1) break;

				/* disable interrupt */
				qe_interrupt_disable();

				rxfifo->buffer[rxfifo->put_index] = c;
				rxfifo->put_index += 1;
				if (rxfifo->put_index >= serial->config.bufsz) rxfifo->put_index = 0;

				/* if the next position is read index, discard this 'read char' */
				if (rxfifo->put_index == rxfifo->get_index)
				{
					rxfifo->get_index += 1;
					rxfifo->is_full = qe_true;
					if (rxfifo->get_index >= serial->config.bufsz) rxfifo->get_index = 0;

					_serial_check_buffer_size();
				}

				/* enable interrupt */
				qe_interrupt_enable();
			}

			/* invoke callback */
			if (serial->parent.rx_indicate != QE_NULL)
			{
				qe_size_t rx_length;

				/* get rx length */
				qe_interrupt_disable();
				rx_length = (rxfifo->put_index >= rxfifo->get_index)? (rxfifo->put_index - rxfifo->get_index):
								(serial->config.bufsz - (rxfifo->get_index - rxfifo->put_index));
				qe_interrupt_enable();

				if (rx_length)
                {
                    serial->parent.rx_indicate(&serial->parent, rx_length);
                }
			}
			break;
		}

		case QE_SERIAL_EVENT_TX_DONE:
		{
			//struct qe_serial_txfifo *txfifo;

			//txfifo = (struct qe_serial_txfifo *)serial->tx;
			//qe_completion_done(&txfifo->completion);
			break;
		}
#if (QE_SERIAL_WITH_DMA == 1)
		case QE_SERIAL_EVENT_TX_DMADONE:
		{
			const void *data_ptr;
			qe_size_t data_size;
			const void *last_data_ptr;
			struct qe_serial_tx_dma *tx_dma;

			tx_dma = (struct qe_serial_tx_dma*) serial->serial_tx;

			qe_data_queue_pop(&(tx_dma->data_queue), &last_data_ptr, &data_size, 0);
			if (qe_data_queue_peek(&(tx_dma->data_queue), &data_ptr, &data_size) == QE_EOK)
			{
				/* transmit next data node */
				tx_dma->activated = qe_true;
				serial->ops->dma_transmit(serial, (qe_u8 *)data_ptr, data_size, QE_SERIAL_DMA_TX);
			}
			else
			{
				tx_dma->activated = qe_false;
			}

			/* invoke callback */
			if (serial->parent.tx_complete != QE_NULL)
			{
				serial->parent.tx_complete(&serial->parent, (void*)last_data_ptr);
			}
			break;
		}
		case QE_SERIAL_EVENT_RX_DMADONE:
		{
			int length;
			qe_base_t level;

			/* get DMA rx length */
			length = (event & (~0xff)) >> 8;

			if (serial->config.bufsz == 0)
			{
				struct qe_serial_rx_dma* rx_dma;

				rx_dma = (struct qe_serial_rx_dma*) serial->serial_rx;
				qe_assert(rx_dma != QE_NULL);

				qe_assert(serial->parent.rx_indicate != QE_NULL);
				serial->parent.rx_indicate(&(serial->parent), length);
				rx_dma->activated = qe_false;
			}
			else
			{
				/* disable interrupt */
				qe_interrupt_disable();
				/* update fifo put index */
				qe_dma_recv_update_put_index(serial, length);
				/* calculate received total length */
				length = qe_dma_calc_recved_len(serial);
				/* enable interrupt */
				qe_interrupt_enable();
				/* invoke callback */
				if (serial->parent.rx_indicate != QE_NULL)
				{
					serial->parent.rx_indicate(&(serial->parent), length);
				}
			}
			break;
		}
#endif /* RT_SERIAL_USING_DMA */
	}
}

static qe_err_t qe_serial_open(struct qe_device *dev, qe_u16 oflag)
{
	qe_u16 stream_flag = 0x0;
	struct qe_serial_device *serial;

	serial = (struct qe_serial_device *)dev;

	/* check device flag with the open flag */
	if ((oflag & QE_DEV_F_DMA_RX) && !(dev->flag & QE_DEV_F_DMA_RX))
		return qe_err_notsupport;
	if ((oflag & QE_DEV_F_DMA_TX) && !(dev->flag & QE_DEV_F_DMA_TX))
		return qe_err_notsupport;
    if ((oflag & QE_DEV_F_INT_RX) && !(dev->flag & QE_DEV_F_INT_RX))
        return qe_err_notsupport;
    if ((oflag & QE_DEV_F_INT_TX) && !(dev->flag & QE_DEV_F_INT_TX)) {
        return qe_err_notsupport;
    }

	/* keep steam flag */
	if ((oflag & QE_DEV_F_STREAM) || (dev->open_flag & QE_DEV_F_STREAM))
		stream_flag = QE_DEV_F_STREAM;

	/* get open flags */
	dev->open_flag = oflag & 0xff;

	/* initialize the Rx/Tx structure according to open flag */
	if (serial->rx == QE_NULL) 
	{
		if (oflag & QE_DEV_F_INT_RX) 
		{
			struct qe_serial_rxfifo *rxfifo;
			rxfifo = serial_rxfifo_create(serial->config.bufsz);
			serial->rx = rxfifo;
			dev->open_flag |= QE_DEV_F_INT_RX;
			serial->ops->control(serial, QE_DEV_CTRL_SET_INT, (void *)QE_DEV_F_INT_RX);
		}
#if (QE_SERIAL_WITH_DMA == 1)
		else if (oflag & QE_DEV_F_DMA_RX) {
			if (serial->config.bufsz == 0) {

				struct qe_serial_rxdma *rxdma;
				rxdma = (struct qe_serial_rxdma *)qe_malloc(sizeof(struct qe_serial_rxdma));
				//qe_assert(rxdma != QE_NULL);
				rxdma->activated = qe_false;
				serial->rx = rxdma;
			} else {
			
				struct qe_serial_rxfifo *rxfifo;
				rxfifo = serial_rxfifo_create(serial->config.bufsz);
                serial->rx = rxfifo;
				serial->ops->control(serial, QE_DEV_CTRL_CONFIG, (void *)QE_DEV_F_DMA_RX);
			}
			dev->open_flag |= QE_DEV_F_DMA_RX;
		}
#endif
		else {
			serial->rx = QE_NULL;
		}
	} else {
		if (oflag & QE_DEV_F_INT_RX)
			dev->open_flag |= QE_DEV_F_INT_RX;
#if (QE_SERIAL_WITH_DMA == 1)
		else if (oflag & QE_DEV_F_DMA_RX)
			dev->open_flag |= QE_DEV_F_DMA_RX;
#endif
	}
	/* set stream flag */	
	dev->open_flag |= stream_flag;

	return qe_ok;
}

static qe_err_t qe_serial_close(struct qe_device *dev)
{
	struct qe_serial_device *serial;

	qe_assert(dev != QE_NULL);
	serial = (struct qe_serial_device *)dev;

	/* this device has more reference count */
	if (dev->reference> 1) return qe_ok;

    if (dev->open_flag & QE_DEV_F_INT_RX) {
        struct qe_serial_rxfifo* rx_fifo;
        /* configure low level device */
        serial->ops->control(serial, QE_DEV_CTRL_CLR_INT, (void*)QE_DEV_F_INT_RX);
        dev->open_flag &= ~QE_DEV_F_INT_RX;
        rx_fifo = (struct qe_serial_rxfifo*)serial->rx;
        qe_assert(rx_fifo != QE_NULL);
        qe_free(rx_fifo);
        serial->rx = QE_NULL;
    }

    serial->ops->control(serial, QE_DEV_CTRL_CLOSE, QE_NULL);
    dev->flag &= ~QE_DEV_F_ACTIVATED;
    
	return qe_ok;
}

static qe_size_t qe_serial_read(struct qe_device *dev,
									  qe_off_t 		    pos,
									  void             *buffer,
									  qe_size_t         size)
{
	struct qe_serial_device *serial;

	qe_assert(dev != QE_NULL);
	if (size == 0) return 0;

	(void)pos;

	serial = (struct qe_serial_device *)dev;

	if (dev->open_flag & QE_DEV_F_INT_RX) {
		return serial_int_rx(serial, (qe_u8 *)buffer, size);
	}
#if (QE_SERIAL_WITH_DMA == 1)
	else if (dev->open_flag & QE_DEV_F_DMA_RX) {
		return serial_dma_rx(serial, (qe_u8 *)buffer, size);
	}
#endif

	return serial_poll_rx(serial, (qe_u8 *)buffer, size);
}

static qe_size_t qe_serial_write(struct qe_device *dev,
								       qe_off_t 		 pos,
									   const void *      buffer,
									   qe_size_t         size)
{
	struct qe_serial_device *serial;

	(void)pos;

	qe_assert(dev != QE_NULL);
	if (size == 0) return 0;

	serial = (struct qe_serial_device *)dev;

	if (dev->open_flag & QE_DEV_F_INT_TX) {
		return serial_int_tx(serial, (const qe_u8 *)buffer, size);
	}
#if (QE_SERIAL_WITH_DMA == 1)
	else if (dev->open_flag & QE_DEV_F_DMA_RX) {
		return serial_dma_tx(serial, (const qe_u8 *)buffer, size);
	}
#endif
	else {
		return serial_poll_tx(serial, (const qe_u8 *)buffer, size);
	}

	return 0;
}

static qe_err_t qe_serial_control(struct qe_device *dev,
										 int 			   cmd,
										 void       	  *args)
{
	qe_err_t ret = qe_ok;
	struct qe_serial_device *serial;

	//qe_assert(dev != QE_NULL);
	serial = (struct qe_serial_device *)dev;

	switch (cmd) {

	case QE_DEV_CTRL_SUSPEND:
		dev->flag |= QE_DEV_F_SUSPENDED;
		break;

	case QE_DEV_CTRL_RESUME:
		dev->flag &= ~QE_DEV_F_SUSPENDED;
		break;

	case QE_DEV_CTRL_CONFIG:
		if (args) {
			struct qe_serial_configure *pcfg = (struct qe_serial_configure *)args;
			if ((pcfg->bufsz != serial->config.bufsz) && (serial->parent.reference)) {
				/*can not change buffer size*/
				return qe_err_busy;
			}
			/* set serial configure */
			serial->config = *pcfg;
			if (serial->parent.reference) {
				/* serial device has been opened, to configure it */
				serial->ops->configure(serial, (struct qe_serial_configure *) args);
			}
		}
		break;

	default:
		ret = serial->ops->control(serial, cmd, args);
		break;
	}

	return ret;
}

static const struct qe_device_ops serial_ops = {
	qe_serial_init,
	qe_serial_open,
	qe_serial_close,
	qe_serial_read,
	qe_serial_write,
	qe_serial_control
};

qe_err_t qe_serial_register(qe_serial_t              *serial,
								   const char 			   *name,
								   qe_u32 			    flag,
								   void 				    *data)
{
	struct qe_device *dev;

	dev = &(serial->parent);

	dev->class = QE_DevClass_Char;
	dev->rx_indicate = QE_NULL;
	dev->tx_complete = QE_NULL;

	dev->ops = &serial_ops;

	dev->private = data;

	return qe_device_register(dev, name, flag);
}


